/**
在 JavaScript 的响应式系统中，自动收集依赖是实现响应式编程的核心机制之一。在 Vue 3 中，依赖的自动收集使得开发者可以轻松地管理状态变化与视图更新。以下是对 Vue 3 中自动收集依赖的说明总结：

  1. 自动依赖收集的概念
  - 自动依赖收集：当一个响应式对象的属性在函数执行过程中被访问时，系统会自动记录下这个访问者（通常是计算属性或副作用函数）。当该属性发生变化时，所有依赖于它的函数都会被重新执行。
  - 这种机制使得开发者无需手动管理依赖关系，从而简化了代码的复杂性。

  2. 使用 Proxy 实现
  Vue 3 使用  `Proxy`  对对象进行代理，拦截对对象属性的访问和修改。具体的依赖收集流程如下：

  - 访问属性：当访问一个响应式对象的属性时，Vue 会触发  `getter` ，在这个过程中，它会记录当前的依赖（即正在执行的副作用函数）。
  - 记录依赖：在  `getter`  中，当前的副作用函数（如  `ReactiveEffect`  实例）会被注册到该属性的依赖列表中。
  - 触发更新：当该属性的值发生变化时，Vue 会触发  `setter` ，并遍历该属性的依赖列表，调用所有依赖的副作用函数，从而更新视图。

  3. 示例
  以下是一个简单的示例，展示了如何在 Vue 3 中实现自动依赖收集：
  import { reactive, effect } from 'vue';

  const state = reactive({ count: 0 });

  // 定义一个响应式的副作用
  effect(() => {
    console.log(`Count is: ${state.count}`);
  });

  // 修改响应式数据
  state.count++; // 当 count 变化时，副作用函数会被重新执行
  4. 效果与优势
  - 简化开发：开发者无需手动管理依赖关系，Vue 会自动处理依赖的注册与更新。
  - 性能优化：只有在依赖的响应式属性发生变化时，相关的副作用函数才会被重新执行，从而避免不必要的计算和渲染。
  - 清晰的逻辑：通过自动收集依赖，代码逻辑变得更加清晰，易于理解和维护。

  5. 注意事项
  - 依赖收集只会在响应式对象的属性被访问时进行，因此在副作用函数中确保访问了需要依赖的属性。
  - 如果副作用函数中不访问任何响应式属性，则不会进行依赖收集，也不会在属性变化时触发更新。

  总结
  在 Vue 3 中，自动收集依赖通过  `Proxy`  和响应式系统实现，使得开发者能够轻松管理状态变化与视图更新。理解自动依赖收集的机制对于掌握 Vue 3 的响应式特性至关重要，它简化了代码的复杂性，提高了开发效率。
 */
/**
  * 1.dep对象数据结构的管理(最难理解)
    * 每一个对象的每一个属性都会对应一个dep对象
    * 同一个对象的多个属性的dep对象是存放一个map对象中
    * 多个对象的map对象, 会被存放到一个objMap的对象中
  * 2.依赖收集: 当执行get函数, 自动的添加fn函数
 */
class Depend {
  constructor() {
    this.reactiveFns = []
  }

  // 添加依赖
  addDepend(fn) {
    if (fn) {
      this.reactiveFns.push(fn)
    }
  }

  // 通知更新
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

const obj = {
  name: 'responsive',
  age: 18
}

// 设置一个专门执行响应式函数的一个函数
let activeReactiveFn = null
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个函数: 负责通过obj的key获取对应的Depend对象
const objMap = new WeakMap()
function getDepend(target, key) {
  // 1.根据对象obj, 找到对应的map对象
  let map = objMap.get(target)
  if (!map) {
    map = new Map()
    objMap.set(target, map)
  }

  // 2.根据key, 找到对应的depend对象
  let dep = map.get(key)
  if (!dep) {
    dep = new Depend()
    map.set(key, dep)
  }

  return dep
}

// 方案一: Object.defineProperty() -> Vue2
Object.keys(obj).forEach(key => {
  let value = obj[key]

  Object.defineProperty(obj, key, {
    get() {
      // 拿到obj -> key
      // console.log("get函数中:", obj, key)
      // 找到对应的obj对象的key对应的dep对象
      const dep = getDepend(obj, key)
      dep.addDepend(reactiveFn)

      return value
    },
    set(newValue) {
      value = newValue
      const dep = getDepend(obj, key)
      dep.notify()
    }
  })
})

// 方案二: Proxy -> Vue3

// ========================= 业务代码 ========================
watchFn(function () {
  console.log(obj.name)
  console.log(obj.age)
  // console.log(obj.age)
})

// watchFn(function() {
//   console.log(obj.address)
// })

// watchFn(function() {
//   console.log(obj.age)
//   console.log(obj.address)
// })

// 修改name
console.log("--------------")
obj.name = "kobe"
obj.age = 20
obj.address = "上海市"